package com.cs252.AudioTest;

import java.util.ArrayList;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

public class AudioRecordTest extends Activity implements Runnable{
	 
	public AudioTrack track;
	
	/**
	 * Communicator handles the connections to the server and sending the data
	 */
	Communicator c;
	
	/**
	 * br takes data from the MIC and puts it into a buffer of bytes and sends to server
	 */
	public bufferRecorder br;
	
	/**
	 * Object used for locks
	 */
	public Object lock;
	
	/**
	 * Another Object for locking
	 */
	public Object listLock = new Object();
	
	/**
	 * Used to show if call is connected or if one phone hung up
	 */
	public boolean running = false;
	
	/**
	 * Used if the minimum buffer size for AudioRecord or AudioTrack is too small
	 */
    private int buff_size = 1024;
    /**
     * Sets the Sample Per Second size for the AudioRecord and Audio Track
     */
	private int samplePerSec = 22050; //Can be 8000, 11025, 22050, 44100, 48000
    
	/**
	 * Used to setup the output and the output setting of any audio played
	 * This includes things such as speaker phone or earpiece for output, volume etc
	 */
	public AudioManager am;
	
	//List of GUI elements being used, these are initialized in onCreate
	//and given action listeners if needed
	/**
	 * Used to record/play back sound, will be changed to just make call
	 */
    private Button callButton;
    /**
     * Used to hang up phone
     */
	private Button stopButton;
	/**
	 * Used to tell the phone to connect to the phone
	 */
	Button connectButton;
	/**
	 * Shows the users currently connected to the server
	 */
	ListView userListView;
	
	/**
	 * Holds the minimum buffer size for the given audio quality to record at
	 */
	private int min_record_size = 0;

	/**
	 * Currently Holds the names of users on the server, but may be replaced with a scroll view
	 */
	public ArrayList<String> names;
	HighlightAdapter array;
	
	/**
	 * The user name of the person on this phone
	 */
	private String name;
	private String serverIP;
	private String port;
	
	private String call_name = "";
	
	public AlertDialog.Builder builder;
	public AlertDialog.Builder incall_builder;
	
	public String fname = "";
		
	/**
	 * Method called when the application activates this is basically main
	 * @author Andrew Shields
	 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	//Have to call the Activity class onCreate method
		super.onCreate(savedInstanceState);
		
		min_record_size = AudioRecord.getMinBufferSize(samplePerSec,  AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
		if(min_record_size < buff_size){
			min_record_size = buff_size;
		}
		//Sets the content view to be the same as the GUI built with the designer
		setContentView(R.layout.main);
		
		//Force the phone not to change layouts
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		
		track = new AudioTrack(AudioManager.STREAM_MUSIC, samplePerSec, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, min_record_size, AudioTrack.MODE_STREAM);
		
		//Initialize lock
		lock = new Object();
		
		//Initialize AudioManager
		am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
		
		
		//Get all GUI elements and put action listeners on them
		callButton = (Button) findViewById(R.id.call_button);
		callButton.setOnClickListener(new Button.OnClickListener(){
			public void onClick(View v){
				callFriend();
			}
		});
		
		stopButton = (Button) findViewById(R.id.end_button);
		stopButton.setOnClickListener(new Button.OnClickListener(){
			public void onClick(View v) {
                synchronized (lock){
                	running = false;
                	lock.notifyAll();
                }
                c.end();
                try{
                	br.join();
                }catch(Exception e){
                	//print exception to a log file
                }
                am.setMode(AudioManager.MODE_NORMAL);
            }
		});
		
		connectButton = (Button)findViewById(R.id.connect_button);
		connectButton.setOnClickListener(new Button.OnClickListener(){
			public void onClick(View v){
				if(c == null){
					//builder.show();
					loadServer();
				}
			}
		});

		//Name Array
		names = new ArrayList<String>();
		array = new HighlightAdapter(this, android.R.layout.simple_expandable_list_item_1, names);
		
		userListView = (ListView)findViewById(R.id.user_list);
		userListView.setAdapter(array);
		userListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
		
		userListView.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View view,
			        int position, long id) {
				if(!names.isEmpty()){
					call_name = names.get(position);
					array.setSelectedIndex(position);
				}
			}
		});
		
		DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
		    public void onClick(DialogInterface dialog, int which) {
		        switch (which){
		        	case DialogInterface.BUTTON_POSITIVE:
		        		//Yes button clicked
		        		c.allow();
		        		break;
		        	case DialogInterface.BUTTON_NEGATIVE:
		        		//No button clicked
		        		c.end();
		        		break;
		        }
		    }
		};
		
		builder = new AlertDialog.Builder(this);
		builder.setPositiveButton("Yes", dialogClickListener).setNegativeButton("No", dialogClickListener);
		builder.setMessage(" is calling you. Would you like to accept?");
		
		incall_builder = new AlertDialog.Builder(this);
		
		Thread thread = new Thread(this);
		thread.start();
		
		SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
		name = settings.getString("username", "name please");
		serverIP = settings.getString("IP", "192.168.1.100");
		port = settings.getString("port", "4400");
		handler.sendEmptyMessage(2);
    }
    
    /**
     * A thread started by the main thread which waits for a signal saying that the List View needs to be updated
     */
    public void run(){
    	
    	while(true)
		{
			try
			{
				synchronized(listLock){
					listLock.wait();
				}
				handler.sendEmptyMessage(0);
			}
			catch(Exception ex) {
				ex.printStackTrace();
			}
		}
    }
    
	/**
	 * A fixer method to allow us to update the List View from other threads
	 */
	public Handler handler = new Handler(){
		@Override
		public void handleMessage(Message msg){
			if(msg.what == 0){
				updateArray();
			}
			else if(msg.what == 1){
				builder.setMessage(fname + " is calling you. Would you like to accept?");
				builder.show();
			}
			else if(msg.what == 2){
				EditText et = (EditText)findViewById(R.id.name_text);
		    	et.setText(name);
		    	et = (EditText)findViewById(R.id.serverIP_text);
		    	et.setText(serverIP);
		    	et = (EditText)findViewById(R.id.port_text);
		    	et.setText(port);
			}
			else if(msg.what == 3){
				incall_builder.setNeutralButton("Ok", null);
				incall_builder.setMessage(call_name + " is currently in a phone call!");
				incall_builder.show();
			}
		}
	};
	/**
	 * Notifies the array that it's data has been changed and needs to update the List View
	 */
	public void updateArray(){
		array.setSelectedIndex(-1);
		for(int i = 0; i < names.size(); i++){
			if(names.get(i).equals(call_name)){
				array.setSelectedIndex(i);
			}			
		}		
		array.notifyDataSetChanged();
	}
	
	
	/**
	 * @author Alan
	 * 
	 * Subclass of ArrayAdapter so we can select and keep highlighted currently selected name
	 */
	
	private class HighlightAdapter extends ArrayAdapter<String>{
		private int selectedIndex = -1;
		
		public HighlightAdapter(Context context, int txtViewResourceID, ArrayList<String> objects){
			super(context, txtViewResourceID, objects);
		}
		
		public void setSelectedIndex(int index){
			selectedIndex = index;
			notifyDataSetChanged();
		}
				
		@Override
		public View getView(int position, View convertView, ViewGroup parent){
			View v = super.getView(position, convertView, parent);
			if(position == selectedIndex){
				v.setBackgroundColor(Color.GREEN);
			}
			else{
				v.setBackgroundColor(Color.BLACK);
			}
			
			return v;
		}
		
	}
	/**
	 * Tells the communicator to call the selected name
	 */
	private void callFriend(){
		if(call_name != ""){
			c.specific(call_name);
		}
	}
	
	/**
	 * Connects the phone to the server
	 */
	private void loadServer(){
    	 EditText et = (EditText)findViewById(R.id.name_text);
    	 name = et.getEditableText().toString();
    	 et = (EditText)findViewById(R.id.serverIP_text);
    	 serverIP = et.getEditableText().toString();
    	 et = (EditText)findViewById(R.id.port_text);
    	 port = et.getEditableText().toString();
    	 try{
    		 c = new Communicator(serverIP, Integer.parseInt(port), name, this, min_record_size);
    		 System.out.println("Break");
    	 }catch(Exception e){
    		 Log.d("Foo didn't work: ",e.getMessage());   
    	}
    }
    
	/**
	 * Has audio manager set up all audio variables such as volume
	 * Starts the bufferRecorder thread
	 * @author Andrew Shields
	 */
    public void startRecording() {
    	
    	//If threads are already running, don't start new ones
    	if(running){
    		return;
    	}
    	
    	//Use AudioManager to force audio through Earpiece, turn off speaker phone
    	//and set all volume settings to be the phone's settings for a phone call
    	am.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.STREAM_MUSIC);
    	setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
    	am.setSpeakerphoneOn(false);
    	am.setMode(AudioManager.MODE_IN_CALL);
    	
    	synchronized (lock) {
			running = true;
			lock.notifyAll();
		}
    	br = new bufferRecorder(MediaRecorder.AudioSource.MIC, samplePerSec, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, min_record_size);
    	br.start();
    }

	/**
	 * Method called when the app is left
	 * @author Andrew Shields
	 */
    @Override
    public void onPause() {
       super.onPause();
       if((br != null && br.isAlive()) || c == null){
    	   return;
       }
       c = null;
	   SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
   		SharedPreferences.Editor editor = settings.edit();
   		editor.putString("username", name);
   		editor.putString("IP", serverIP);
   		editor.putString("port", port);
		editor.commit();
       System.gc();
	   android.os.Process.killProcess(android.os.Process.myPid());
       System.exit(0);
    }
	
	@Override
    public void onStop() {
    	super.onStop();
    	c = null;
    	SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
    	SharedPreferences.Editor editor = settings.edit();
    	EditText et = (EditText)findViewById(R.id.name_text);
   	 	name = et.getEditableText().toString();
   	 	et = (EditText)findViewById(R.id.serverIP_text);
   	 	serverIP = et.getEditableText().toString();
   	 	et = (EditText)findViewById(R.id.port_text);
   	 	port = et.getEditableText().toString();
    	editor.putString("username", name);
    	editor.putString("IP", serverIP);
    	editor.putString("port", port);
    	editor.commit();
    	System.gc();
    	android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(0);
    }
    
    /**
     * Plays a buffer through the currently enabled audio out
     * @param buff A buffer of type bytes
     * @param offset Where to start playing in the buffer usually 0
     * @param len How much of the buffer to play, usually just the length of the buffer
     */
    public void playBuffer(byte []buff, int offset, int len){
    	track.play();
    	track.write(buff, offset, len);
    	track.stop();
    }
    
    /**
     * Thread records data from MIC and for now plays back on Earpiece, but will be changed
     * to send the data to the server and not playback
     * @author Andrew
     */
    public class bufferRecorder extends Thread{
    	public byte []buff;
    	public AudioRecord arecord;
    	public int size;
    	
    	//construct a new thread
    	//needs all arguments passed to AudioRecord constructor in order
    	/**
    	 * @author Andrew Shields
    	 */
    	public bufferRecorder(int stream, int samplePers, int channel_config, int encode, int min_size){
    		arecord = new AudioRecord(stream, samplePers, channel_config, encode, min_size);
    		buff = new byte[min_size + 5];
    		size = min_size;		
    	}
    	
    	public void run(){
        	//Starting the AudioRecord to be ready to record
    		arecord.startRecording();
    		
    		//Used a while loop to read data while phone is making a call
    		//This will be changed to having record.read(), and then sending the buffer
    		//to the server rather than playing it
    		
    		while(true){
    			synchronized (lock){
    				if(!running){
    					lock.notifyAll();
    					break;
    				}
    			}
    			//Fills buffer with audio data
    			int read = arecord.read(buff, 5, min_record_size);
    			try{
    				c.sendBuffer(buff);
    			}catch(Exception g){}
    		}
    		
    		//Stop the AudioRecord and the AudioTrack
    		arecord.stop();
    		
    		//Set the phones audio back to normal setting mode
    		am.setMode(AudioManager.MODE_NORMAL);
    	}
    }
}